﻿using System;
using System.Collections.Generic;
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.Configuration;
using Se.Framework.Common.Extentions;
using Se.Framework.Common.ObjectRegistrySupport;

namespace Se.Framework.Common
{
    /// <summary>
    /// Class for Dependency Injection using Unity
    /// </summary>
    public class UnityObjectRegistry : IObjectRegistry
    {
        public UnityObjectRegistry()
        {
            InitializeContainer();
        }

        /// <summary>
        /// Gets the container provided by Unity.
        /// </summary>
        public IUnityContainer Container { get; private set; }

        /// <summary>
        /// Initializes the container.
        /// </summary>
        private void InitializeContainer()
        {
            this.Container = new UnityContainer();
            this.Container.LoadConfiguration();
        }

        #region IObjectRegistry

        public void RegisterInstance(Type typeFrom, object instanceTo)
        {
            this.Container.RegisterInstance(typeFrom, instanceTo, (LifetimeManager)UnityObjectLifetimeFactory.Instance.CreateFrom(instanceTo.GetType()));
        }

        public void RegisterInstance<TTypeFrom>(TTypeFrom instanceTo)
        {
            this.RegisterInstance(typeof(TTypeFrom), instanceTo);
        }

        public void RegisterInstance(Type typeFrom, string name, object instanceTo)
        {
            this.Container.RegisterInstance(typeFrom, name, instanceTo, (LifetimeManager)UnityObjectLifetimeFactory.Instance.CreateFrom(instanceTo.GetType()));
        }

        public void RegisterType(Type typeFrom)
        {
            this.Container.RegisterType(typeFrom, (LifetimeManager)UnityObjectLifetimeFactory.Instance.CreateFrom(typeFrom));
        }

        public void RegisterType(Type typeFrom, Type typeTo)
        {
            this.Container.RegisterType(typeFrom, typeTo, (LifetimeManager)UnityObjectLifetimeFactory.Instance.CreateFrom(typeTo));
        }

        public void RegisterType<T>()
        {
            this.RegisterType(typeof(T));
        }

        public void RegisterType<TFrom, TTo>()
        {
            this.RegisterType(typeof(TFrom), typeof(TTo));
        }

        public void Reset()
        {
            InitializeContainer();
        }

        public object Resolve(Type type)
        {
            return this.Container.Resolve(type);
        }

        public IEnumerable<object> ResolveAll(Type type)
        {
            return this.Container.ResolveAll(type);
        }

        public object Resolve(Type type, string name)
        {
            return this.Container.Resolve(type, name);
        }

        public T Resolve<T>() where T : class
        {
            return this.Container.Resolve<T>();
        }

        public T Resolve<T>(string name) where T : class
        {
            return this.Container.Resolve<T>(name);
        }

        public Lazy<object> ResolveLazy(Type type)
        {
            return new Lazy<object>(() => this.Container.Resolve(type));
        }

        public Lazy<object> ResolveLazy(Type type, string name)
        {
            return new Lazy<object>(() => this.Container.Resolve(type, name));
        }

        public Lazy<T> ResolveLazy<T>() where T : class
        {
            return new Lazy<T>(() => this.Container.Resolve<T>());
        }

        public Lazy<T> ResolveLazy<T>(string name) where T : class
        {
            return new Lazy<T>(() => this.Container.Resolve<T>(name));
        }

        public bool IsRegistered(Type type, string name)
        {
            return this.Container.IsRegistered(type, name);
        }

        public bool IsRegistered(Type type)
        {
            return this.Container.IsRegistered(type);
        }

        public bool IsRegistered<T>()
            where T : class
        {
            return this.Container.IsRegistered<T>();
        }

        public bool IsRegistered<T>(string name)
            where T : class
        {
            return this.Container.IsRegistered<T>(name);
        }

        public void Clean(object instance)
        {
            Container.Teardown(instance);
        }

        #endregion

        private class UnityObjectLifetimeFactory : ObjectRegistrySupport.IObjectLifetimeFactory
        {
            public static readonly UnityObjectLifetimeFactory Instance = new UnityObjectLifetimeFactory();

            private UnityObjectLifetimeFactory()
            {

            }

            public object CreateFrom(ObjectLifetimeType type)
            {
                return GetLifetimeManager(type);
            }

            public object CreateFrom(Type type)
            {
                var attr = type.Attribute<ObjectLifetimeAttribute>();
                if (attr != null)
                    return CreateFrom(attr.Type);

                return GetLifetimeManager(ObjectLifetimeType.EveryCall);
            }

            private LifetimeManager GetLifetimeManager(ObjectLifetimeType type)
            {
                LifetimeManager result = null;
                switch (type)
                {
                    case ObjectLifetimeType.Singleton:
                        result = new ContainerControlledLifetimeManager();
                        break;
                    case ObjectLifetimeType.PerThread:
                        result = new PerThreadLifetimeManager();
                        break;
                    case ObjectLifetimeType.PerCall:
                        result = new CallContextLifetimeManager();
                        break;
                    case ObjectLifetimeType.EveryCall:
                    default:
                        result = new TransientLifetimeManager();
                        break;
                }
                return result;
            }
        }
    }
}